#ifndef CUEPOINTS_H
#define CUEPOINTS_H

#include <assert.h>
#include <iostream>

#include "boost/config.hpp"
#include "boost/archive/tmpdir.hpp"
#include "boost/archive/xml_iarchive.hpp"
#include "boost/archive/xml_oarchive.hpp"


#include <iostream>
#include <fstream>
#include <boost/config.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp> 
#include "boost/archive/tmpdir.hpp"
#include "boost/archive/xml_iarchive.hpp"
#include "boost/archive/xml_oarchive.hpp"
#include "boost/archive/text_iarchive.hpp"
#include "boost/archive/text_oarchive.hpp"

namespace SnackBreak
{

// Basic cue point structure representing a point in time in a track.
// An absolute position in seconds corresponds to a specific beat (or fraction of a beat).
// E.g. the standard cue point is beat zero, usually the first note of the track.
class CuePoint
{
public:
	CuePoint() : beatNumber(0.0), secondsTime(0.0) {};  
	CuePoint(double beat, double secs) : beatNumber(beat), secondsTime(secs) {};  

	double beatNumber;
	double secondsTime;
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar  & beatNumber & secondsTime;
    }
};


// Very basic cue logic - two points only.
// Sufficient if tempo is constant throughout whole song.
// Limitations:
//  - cannot know end of song (see track!)
//  - does not hande changing tempo or sections with no tempo
class CuePair
{
public:
	CuePair(CuePoint aa, CuePoint bb) : a(aa), b(bb) { };

	// Return the duration of a single beat (i.e. onset to onset) in seconds.
	double myBeatLength() const 
	{
		assert((b.beatNumber - a.beatNumber) != 0);
		
		if ((b.beatNumber - a.beatNumber) != 0)
		{
			double dur = b.secondsTime - a.secondsTime;
			return dur / (b.beatNumber - a.beatNumber);
		}
		else
			return 1.0; // hmm, maybe 1 sec per beat!
	}

	// Return the duration of a single beat (i.e. onset to onset) in milliseconds.
	double myBeatLengthMsec() const 
	{
		return myBeatLength() * 1000.0;
	}

	// Returns the location (in seconds) of the start of the track.
	// (i.e. the standard/canonical/CDJ "cue point")
	double myZerothBeatIsAt() const 
	{
		return a.secondsTime - (myBeatLength() * a.beatNumber);
	}

	// Returns the location (in milliseconds) of the start of the track.
	// (i.e. the standard/canonical/CDJ "cue point")
	double myZerothBeatIsAtMsec() const 
	{
		return myZerothBeatIsAt() * 1000.0;
	}

	// Returns the location (in seconds) of a given beat in the track.
	// Note that it is possible to request a fraction of a beat (i.e. off beat etc).
	double myNthBeatIsAt(double beatIndex) const 
	{
		return myZerothBeatIsAt() + beatIndex * myBeatLength();
	}

	// Returns the location (in milliseconds) of a given beat in the track.
	// Note that it is possible to request a fraction of a beat (i.e. off beat etc).
	double myNthBeatIsAtMsec(double beatIndex) const 
	{
		double msec = myNthBeatIsAt(beatIndex) * 1000.0;
		//std::cout << "beat " << beatIndex << " is at " << msec << " msec" << std::endl;
		return msec;
	}

	// Returns the beat number at time msec.
	double msecToBeatNum(double msec) const
	{
		double beatNum = (msec - myZerothBeatIsAtMsec()) / myBeatLengthMsec();
		//std::cout << msec << " msec is beat " << beatNum << std::endl;
		return beatNum;
	}

	// Returns the ratio of this track's tempo to another.
	// E.g. use a sample rate calculated by
	//  incomingTrackSampleRate = defaultSampleRate * outgoing.pitchAdjustmentForOtherBeatLen(incoming.myBeatLength())
	// to match the tempo of a track to the tempo represented by this CuePair.
	double pitchAdjustmentForOtherBeatLen(double otherBeatLen) const 
	{
		return otherBeatLen / myBeatLength();
	}

private:
	CuePoint a, b;

private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
    // Could use boost::serialization::make_nvp() directly here and give a and b better names; really though this just indicates that we should have 
    // a list of one or more cue points. (moot point until xml is working anyway)
       ar  & a & b;
    }
};

class TrackCuePair : public CuePair
{
public:
    TrackCuePair() : CuePair(CuePoint(0, 0), CuePoint(0, 0)), lastBeatInTrack(0) {};
//	TrackCuePair(double ab, double as, double ba, double bs) : a(ab, as), b(ba, bs) { };
//	TrackCuePair(CuePoint aa, CuePoint bb, double lastBeat = 999999) : CuePair(aa, bb), lastBeatInTrack(lastBeat) { };
	TrackCuePair(CuePair aabb, double lastBeat = 999999) : CuePair(aabb), lastBeatInTrack(lastBeat) { };

public: // TODO make this private and move the methods that use it from TrackImpl/Track to here
	double lastBeatInTrack;    
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
       ar & boost::serialization::base_object<CuePair>(*this) & lastBeatInTrack;
    }
};

}; // namespace SnackBreak

#endif // CUEPOINTS_H
